import com.jcraft.jsch.ChannelExec
import com.jcraft.jsch.ChannelSftp
import com.jcraft.jsch.JSch
import org.tmatesoft.svn.core.SVNLogEntry
import org.tmatesoft.svn.core.SVNNodeKind
import org.tmatesoft.svn.core.SVNURL
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory
import org.tmatesoft.svn.core.io.SVNRepositoryFactory
import org.tmatesoft.svn.core.wc.SVNWCUtil
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.*
import java.util.zip.CRC32
import java.util.zip.CheckedOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.collections.HashSet


// Eclipse版
//const val projectPath = "/Users/zhangweihua/eclipse-workspace/hjzx_base"
//const val projectPath = "/Users/zhangweihua/IdeaProjects/hjzx"

// IDEA
const val projectPath = "/Users/zhangweihua/IdeaProjects/base"

const val projectName = "base"

const val webContentName = "webapp"

const val targetPath = "/Users/zhangweihua/dev/update_pkg"

const val zipFilePath = "/Users/zhangweihua/dev/update_zip"

const val svnUrl = "https://192.168.3.12/svn/base"

const val name = "zhangwh"

const val password = "zhangwh"

// 开始版本号
const val startRevision: Long = 1574
// 结束版本号
const val endRevision: Long = 1632

// 其他版本号
val revisionArray = arrayOf<Long>()

// 根据日期
val datedArray = arrayOf<String>()

// 忽略文件
val ignoredFiles = arrayOf<String>("/webapp/WEB-INF/web.xml")

// 只查询该SVN用户的提交记录
val onlySvnUsers = arrayOf<String>("zhangwh")

val excludeSvnUsers = arrayOf<String>()

// 是否清空历史文件
const val clearTargetDir = true

// 是否打成压缩包
const val packingZip = true

// 是否部署到服务器
const val isDeployment = false

// 是否自动重启tomcat（如果有class文件则重启，没有则不重启）
const val autoRestartTomcat = false

var hashSet = HashSet<String>()

var copiedFiles = LinkedList<String>()

var hasClasses = false

const val serverHost = "192.168.3.12"
const val serverUser = "pi"
const val serverPassword = "pi123"
const val serverDeploymentPath = "/home/pi/ace/apache-tomcat-9.0.7/webapps/test"
const val serverTomcatBinPath = "/home/pi/ace/apache-tomcat-9.0.7/bin"

fun main(args: Array<String>) {
    getSVNChangeLog()
    if (clearTargetDir) {
        deleteDir("${targetPath}/${projectName}")
        println("删除完成：$projectName")
    }

    // 过滤文件
    hashSet = hashSet.dropWhile { ignoredFiles.contains(it) }.toHashSet()

    if (hashSet.isEmpty()) {
        println("文件列表为空")
        return
    }

    hashSet.filter { it.startsWith("/src") && it.endsWith(".java") }
            .map { it.replace("/src", "$projectPath/$webContentName/WEB-INF/classes") }
            .forEach { findJavaInnerClass(it) }

    hashSet.filter { it.startsWith("/src") && !it.endsWith(".java") }
            .map { it.replace("/src", "$projectPath/$webContentName/WEB-INF/classes") }
            .forEach { findResourceFile(it) }

    hashSet.filter { it.startsWith("/$webContentName") }
            .map { "$projectPath$it" }
            .forEach { findResourceFile(it) }

    if (packingZip) {
        val targetZipFilePath = "$zipFilePath/$projectName.zip"
        compressSubDir("$targetPath/$projectName", targetZipFilePath)
        println("\n打包完成：$targetZipFilePath")
    }

    if (isDeployment) {
        deploy()
    }
}

fun findJavaInnerClass(sourceName: String) {
    val className = sourceName.substringAfterLast("/").split(".").first()
    val dir = sourceName.substringBeforeLast("/")

    // 查找java内部类
    File(dir).listFiles().filter { "$className(\\$\\S*)?.class".toRegex().matches(it.name) }
            .forEach { copyFileToTargetDir(it) }
}

fun findResourceFile(sourceName: String) {
    copyFileToTargetDir(File(sourceName))
}

fun copyFileToTargetDir(sourceFile: File) {
    val relativePath = sourceFile.canonicalPath.replace("$projectPath/$webContentName".replace("/", File.separator), "")
    val targetFileStr = "$targetPath/$projectName$relativePath"
    try {
        sourceFile.copyTo(File(targetFileStr), true)
        if (targetFileStr.endsWith(".class")) {
            hasClasses = true
        }
    } catch (e: Exception) {
        println("文件不存在：$targetFileStr")
    }
    println("复制完成：$targetFileStr")
}

fun deleteDir(dirPath: String) {
    File(dirPath).walkBottomUp().forEach { it.delete() }
}

fun compressSubDir(srcPath: String, destPath: String) {
    val destFile = File(destPath)
    val cos = CheckedOutputStream(FileOutputStream(destFile), CRC32())
    val zos = ZipOutputStream(cos)
    val srcFile = File(srcPath)
    if (srcFile.isDirectory) {
        srcFile.listFiles().forEach {
            compress(it, zos, "")
        }
    }
    zos.close()
    cos.close()
}

fun compress(file: File, zos: ZipOutputStream, baseDir: String) {
    if (file.isDirectory) {
        val filePath = baseDir + file.name + File.separator
        file.listFiles().forEach {
            compress(it, zos, filePath)
        }
    } else {
        // 忽略mac中的.DS_Store文件
        if (!file.name.equals(".DS_Store")) {
            zos.putNextEntry(ZipEntry(baseDir + file.name))
            zos.write(file.readBytes())
        }
    }
}

/**
 * 扩展Date
 */
fun Date.parseExpect(source: String, pattern: String = "yyyy-MM-dd"): Date {
    return SimpleDateFormat(pattern).parse(source)
}

fun Date.plusDate(amount: Int): Date {
    val today = Calendar.getInstance()
    today.add(Calendar.DATE, amount)
    return Date.from(today.toInstant())
}

fun getSVNChangeLog() {
    DAVRepositoryFactory.setup()
    val repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(svnUrl));
    val authManager = SVNWCUtil.createDefaultAuthenticationManager(name, password.toCharArray())
    repository.authenticationManager = authManager
    val stringArray = arrayOf("")
    val logEntries: LinkedList<SVNLogEntry> = LinkedList()

    if (startRevision > 0L) {
        repository.log(stringArray, logEntries, startRevision, endRevision, true, true)
    }

    val dateFormat = SimpleDateFormat("yyyy-MM-dd")
    datedArray.forEach {
        val today = dateFormat.parse(it)
        val minRevision = repository.getDatedRevision(today)
        val maxRevision = repository.getDatedRevision(today.plusDate(1))
        repository.log(stringArray, logEntries, minRevision, maxRevision, true, true)
    }

    revisionArray.forEach {
        repository.log(stringArray, logEntries, it, it, true, true)
    }

    logEntries.forEach {
        //        if (it.author == "hehe_zhangwh") {
//            it.changedPaths.forEach { k, v ->
//                if ("MA".contains(v.type) && SVNNodeKind.FILE == v.kind) {
//                    hashSet.add(v.path.substringAfter(projectName))
//                }
//            }
//        }
        if (onlySvnUsers.isEmpty()) {
            it.changedPaths.forEach { k, v ->
                if ("MA".contains(v.type) && SVNNodeKind.FILE == v.kind) {
                    if (!excludeSvnUsers.contains(it.author)) {
                        hashSet.add(v.path.substringAfter(projectName))
                    }
                }
            }
        } else if (onlySvnUsers.contains(it.author)) {
            it.changedPaths.forEach { k, v ->
                if ("MA".contains(v.type) && SVNNodeKind.FILE == v.kind) {
                    hashSet.add(v.path.substringAfter(projectName))
                }
            }
        }
    }
}

fun deploy() {
    println("\n\n--------------------------开始部署--------------------------")
    val jsch = JSch()
    val session = jsch.getSession(serverUser, serverHost, 22)
    session.setConfig("PreferredAuthentications", "password")
    session.setConfig("StrictHostKeyChecking", "no")
    session.setPassword(serverPassword)
    session.connect(3000)    // making a connection with timeout.
    if (session.isConnected) {
        println("连接服务器成功")
    } else {
        println("连接服务器失败")
        return
    }

    val channelSftp: ChannelSftp = session.openChannel("sftp") as ChannelSftp
    channelSftp.connect()
    File("$targetPath/$projectName").listFiles().forEach {
        copyFileToServer(it, serverDeploymentPath, channelSftp)
    }

    if (autoRestartTomcat && hasClasses) {
        println("\n\n--------------------------重启Tomcat--------------------------")
        val channel: ChannelExec = session.openChannel("exec") as ChannelExec
        channel.setCommand("cd $serverTomcatBinPath && ./shutdown.sh && ./startup.sh")
        channel.connect()

        channel.inputStream.bufferedReader().readLines().forEach(::println)
        channel.disconnect()
    }

    channelSftp.disconnect()
    session.disconnect()
}

fun copyFileToServer(srcFile: File, dst: String, sftp: ChannelSftp) {
    if (srcFile.isDirectory) {
        try {
            sftp.cd(dst)
            sftp.mkdir((srcFile.name))
        } catch (e: Exception) {
//            e.printStackTrace()
        }
        srcFile.listFiles().forEach {
            copyFileToServer(it, "$dst/${srcFile.name}", sftp)
        }
    } else {
        sftp.put(FileInputStream(srcFile), "$dst/${srcFile.name}")
        println("部署成功：$dst/${srcFile.name}")
    }
}
